package server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

/**
 * Mock IPTablesComm class for testing
 */
class IPTablesCommMock extends IPTablesComm {

    boolean failNext; // Fail the next action?
	// number of times each function was called
	int totalFlush = 0;
	int totalAdd = 0;
	int totalRemove = 0;

	// Not part of interface - used for testing
	public void reset() throws Exception {
		flush();
		failNext = false;
		totalFlush = 0;
		totalAdd = 0;
		totalRemove = 0;
	}

	// Not part of interface - used for testing
	public void enableLog() throws Exception {
		ArrayList<MockRule> newRules = new ArrayList<MockRule>();
		for (MockRule mr : currentInRules) {
			newRules.add(new MockRule(mr.rule, true, mr.regexp));
			if (mr.rule.type == Rule.typeOpt.IP) {
				// If it's IP (and not context) we need to add an extra "log" rule
				newRules.add(new MockRule(mr.rule, false, mr.regexp));
			}
		}
		currentInRules = newRules;

		newRules = new ArrayList<MockRule>();
		for (MockRule mr : currentOutRules) {
			newRules.add(new MockRule(mr.rule, true, mr.regexp));
			if (mr.rule.type == Rule.typeOpt.IP) {
				// If it's IP (and not context) we need to add an extra "log" rule
				newRules.add(new MockRule(mr.rule, false, mr.regexp));
			}
		}
		currentOutRules = newRules;
		updateIndices();
	}

	// Not part of interface - used for testing
	public void disableLog() throws Exception {
		ArrayList<MockRule> newRules = new ArrayList<MockRule>();
		for (MockRule mr : currentInRules) {
			if (((mr.rule.type == Rule.typeOpt.IP) && (!mr.log)) ||
				(mr.rule.type == Rule.typeOpt.CONTEXT)) {

				newRules.add(new MockRule(mr.rule, false, mr.regexp));
			}
		}
		currentInRules = newRules;

		newRules = new ArrayList<MockRule>();
		for (MockRule mr : currentOutRules) {
			if (((mr.rule.type == Rule.typeOpt.IP) && (!mr.log)) ||
				(mr.rule.type == Rule.typeOpt.CONTEXT)) {

				newRules.add(new MockRule(mr.rule, false, mr.regexp));
			}
		}
		currentOutRules = newRules;
		updateIndices();
	}

	private Rule dupRule(Rule rule) {
		Rule result;
		if (rule.type == Rule.typeOpt.IP) {
			result = new Rule(rule.index, rule.iprange, rule.port, rule.permission);
		} else {
			result = new Rule(rule.index, rule.port, rule.protocol, rule.permission);
		}

		return result;
	}


	// Not part of interface - used for testing
	public void updateIndices() {
		int i;
		for (i = 0; i < currentInRules.size(); ++i) {
			currentInRules.get(i).rule.index = i + 1;
		}
		for (i = 0; i < currentOutRules.size(); ++i) {
			currentOutRules.get(i).rule.index = i + 1;
		}
	}
			
    // Cause the next operation (whatever it is) to fail
    public void failNextRequest() {
        failNext = true;
    }

    /**
     * Flushes the IPTables rule table by executing iptables -F on our table.
     *
     * @throw FirewallException  on any error
     */
	void flush() throws FirewallException {

		totalFlush ++;

        if (failNext) {
            failRequest();
        }

        currentInRules.clear();
        currentOutRules.clear();
	}

	void add(RulesTableType dir, Rule rule, boolean log, String regexp)
        throws FirewallException {

		totalAdd ++;

        if (failNext) {
            failRequest();
        }

        List<MockRule> targetList;
        targetList = 
            (dir.toString().equals("Input")) ? 
            currentInRules : 
            currentOutRules;

        // TODO - rule validity check?

		if (rule.type == Rule.typeOpt.IP) {
			targetList.add(rule.index - 1, new MockRule(dupRule(rule), false, regexp));
			if (log) {
				targetList.add(rule.index - 1, new MockRule(dupRule(rule), true, regexp));
			}
		} else {	// context rule
			targetList.add(rule.index - 1, new MockRule(dupRule(rule), log, regexp));
		}

		updateIndices();
	}

	void remove(RulesTableType dir, int index) throws FirewallException {

		totalRemove ++;

        if (failNext) {
            failRequest();
        }

        List targetList;
        targetList = 
            (dir.toString().equals("Input")) ? 
            currentInRules : 
            currentOutRules;

        if ((index - 1)>= targetList.size()) {
            throw new FirewallException(
                    "Mock IPTables: remove(index = " + index + "), but only " + 
                    targetList.size() + " rules in " +
                    dir.toString() + " table");
        }

        targetList.remove(index - 1);
		updateIndices();
	}

    private void failRequest() throws FirewallException
    {
        failNext = false;
        throw new FirewallException("MockIPTables: Failing by request");
    }

    List<MockRule> currentInRules = new ArrayList<MockRule>();
    List<MockRule> currentOutRules = new ArrayList<MockRule>();

    static public class MockRule {
        public Rule rule;
        public boolean log;
        public String regexp;

		public MockRule(Rule rule, boolean log, String regexp) {
			this.rule = rule;
			this.log = log;
			this.regexp = regexp;
		}

		public boolean equals(Object other) {
			if (!(other instanceof MockRule)) {
				return false;
			}

			MockRule o = (MockRule)other;
			return (
					(((rule == null) && (o.rule == null)) || 
					 (o.rule.equals(rule))) &&
					(((regexp == null) && (o.regexp == null)) || 
					 (o.regexp.equals(regexp))) &&
					(log == o.log)
				   );
		}

	}
}
